package com.ahccc.codegenerator.backend.mybatis;

import com.ahccc.codegenerator.Zip;
import com.ahccc.codegenerator.entity.Task;
import com.ahccc.codegenerator.vo.DbConnInfo;
import com.ahccc.codegenerator.vo.ProjectInfo;
import com.ahccc.framework.common.text.SymbolConst;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

/**
 * @author lichunqing
 * @date 2019-03-13
 */

@Data
@Slf4j
@Accessors(chain = true)
@Component
public class MybatisBackendGenerator {

    /**
     * 配置信息
     */
    protected ConfigBuilder configBuilder;
    /**
     * 注入配置
     */
    @Getter(AccessLevel.NONE)
    @Setter(AccessLevel.NONE)
    protected InjectionConfig injectionConfig;
    /**
     * 数据源配置
     */
    private DataSourceConfig dataSourceConfig;
    /**
     * 数据库表配置
     */
    private StrategyConfig strategyConfig;
    /**
     * 包 相关配置
     */
    private PackageConfig packageConfig;
    /**
     * 模板 相关配置
     */
    private TemplateConfig templateConfig;
    /**
     * 全局 相关配置
     */
    private GlobalConfig globalConfig;
    /**
     * 模板引擎
     */
    private AbstractTemplateEngine templateEngine;

    @Value("${generate.config.path}")
    private String generateConfigPath;

    private String outDir;

    private String serverOutDir;

    private String parentOutDir;

    private String webOutDir;

    public String execute(Task task, String[] selectedTableNames) throws Exception {
        return this.init(task, selectedTableNames);
    }

    public String init(Task task, String[] selectedTableNames) throws Exception {

        this.outDir = this.generateConfigPath + task.getPackageName();
        this.parentOutDir = outDir + SymbolConst.SLASH + "parent";
        this.serverOutDir = outDir + SymbolConst.SLASH + org.apache.commons.lang3.StringUtils.uncapitalize(task.getModuleName()) + "-server";
        this.webOutDir = outDir + SymbolConst.SLASH + org.apache.commons.lang3.StringUtils.uncapitalize(task.getModuleName()) + "-web";

        this.globalConfig = this.getGlobalConfig(task);
        this.dataSourceConfig = this.getDataSourceConfig(task);
        this.packageConfig = this.getPackageConfig(task);
        this.strategyConfig = this.getStrategyConfig(selectedTableNames);

        this.templateConfig = this.getTemplateConfig();
        this.templateEngine = new FreemarkerTemplateEngine();

        this.configBuilder = new ConfigBuilder(packageConfig, dataSourceConfig, strategyConfig, templateConfig, globalConfig);
        this.injectionConfig = this.getInjectConfig(task);
        this.injectionConfig.setConfig(configBuilder);
        // configBuilder的PathInfo可以自己配置

        // 模板引擎初始化执行文件输出
        templateEngine.init(this.pretreatmentConfigBuilder(configBuilder)).mkdirs().batchOutput().open();

        String zipFileName = outDir + ".zip";
        Zip.zipDirectory(outDir, zipFileName);
        return zipFileName;
    }

    public GlobalConfig getGlobalConfig(Task task){
        GlobalConfig globalConfig = new GlobalConfig();
        try {
            Path outDirPath = Paths.get(outDir);
            FileUtils.deleteDirectory(new File(outDir));
            Files.createDirectories(outDirPath);
        } catch (IOException e) {
            e.printStackTrace();
        }

        globalConfig.setOutputDir(serverOutDir + "/src/main/java");
        globalConfig.setAuthor(task.getAuthor());
        globalConfig.setOpen(false);
        globalConfig.setFileOverride(true);
        return globalConfig;
    }

    public DataSourceConfig getDataSourceConfig(Task task){
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl(DbConnInfo.getMySqlUrl(task.getDbHost(), task.getDbPort(), task.getDbSchema()));
        // dsc.setSchemaName("public");
        dsc.setDriverName(DbConnInfo.getDriveName(task.getDbType()));
        dsc.setUsername(task.getDbUsername());
        dsc.setPassword(task.getDbPassword());
        return dsc;
    }

    private PackageConfig getPackageConfig(Task task){
        PackageConfig pc = new PackageConfig();
        pc.setParent(task.getGroupId() + "." + task.getArtifactId());
        pc.setModuleName(task.getModuleName());
        return pc;
    }

    private InjectionConfig getInjectConfig(Task task){

        /*
         * 自定义配置内容
         * ${cfg.driverClassName}
         */
        InjectionConfig injectionConfig =  new InjectionConfig() {

            @Override
            public void initMap() {
                Map<String, Object> map = new HashMap<>();
                // 将task对象转成Map
                map.putAll((Map<? extends String, ?>) JSON.toJSON(task));
                map.put("driverClassName", DbConnInfo.getDriveName(task.getDbType()));
                map.put("url", DbConnInfo.getUrl(task.getDbType(), task.getDbHost(), task.getDbPort(), task.getDbSchema()));
                this.setMap(map);
            }
        };

        // 自定义模板
        List<FileOutConfig> focList = new ArrayList<>();

        /*
         * 项目parent输出内容
         */
        focList.add(new FileOutConfig(Const.MYBATIS_TEMPLATE_PARENT_POM) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return parentOutDir + "/pom.xml";
            }
        });

        /*
         * 后端输出内容
         */
        focList.add(new FileOutConfig(Const.MYBATIS_TEMPLATE_POM) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return serverOutDir + "/pom.xml";
            }
        });
        focList.add(new FileOutConfig(Const.MYBATIS_TEMPLATE_APPLICATION_YML) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return serverOutDir + "/src/main/resources/application.yml";
            }
        });
        focList.add(new FileOutConfig(Const.MYBATIS_TEMPLATE_APPLICATION) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return  joinPath(serverOutDir + "/src/main/java/", packageConfig.getParent()) + "/" + StringUtils.capitalize(packageConfig.getModuleName()) + "ServerApplication.java";
            }
        });
        focList.add(new FileOutConfig(Const.MYBATIS_TEMPLATE_SERVLET_INITIALIZER) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return  joinPath(serverOutDir + "/src/main/java/", packageConfig.getParent()) + "/ServletInitializer.java";
            }
        });
        focList.add(new FileOutConfig(Const.MYBATIS_TEMPLATE_APPLICATION_TEST) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return  joinPath(serverOutDir + "/src/test/java/", packageConfig.getParent())  + "/" + StringUtils.capitalize(packageConfig.getModuleName()) + "ServerApplicationTests.java";
            }
        });

        /**
         * 前端输出内容
         */
        focList.add(new FileOutConfig(Const.ELEM_TEMPLATE_POM) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return webOutDir + "/pom.xml";
            }
        });
        focList.add(new FileOutConfig(Const.ELEM_TEMPLATE_APPLICATION_YML) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return webOutDir + "/src/main/resources/application.yml";
            }
        });
        focList.add(new FileOutConfig(Const.ELEM_TEMPLATE_APPLICATION) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return  joinPath(webOutDir + "/src/main/java/", packageConfig.getParent()) + "/" + StringUtils.capitalize(packageConfig.getModuleName()) + "WebApplication.java";
            }
        });
        focList.add(new FileOutConfig(Const.ELEM_TEMPLATE_SERVLET_INITIALIZER) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return  joinPath(webOutDir + "/src/main/java/", packageConfig.getParent()) + "/ServletInitializer.java";
            }
        });
        focList.add(new FileOutConfig(Const.ELEM_TEMPLATE_VIEW_CONTROLLER) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return  joinPath(webOutDir + "/src/main/java/", packageConfig.getParent()) + "/ViewController.java";
            }
        });
        focList.add(new FileOutConfig(Const.ELEM_TEMPLATE_APPLICATION_TEST) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 ， 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化！！
                return  joinPath(webOutDir + "/src/test/java/", packageConfig.getParent())  + "/" + StringUtils.capitalize(packageConfig.getModuleName()) + "WebApplicationTests.java";
            }
        });
        focList.add(new FileOutConfig(Const.ELEM_TEMPLATE_INCLUDE_HTML) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return webOutDir + "/src/main/resources/templates/include.html";
            }
        });
        focList.add(new FileOutConfig(Const.ELEM_TEMPLATE_HTML) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return webOutDir + "/src/main/resources/templates/view/" + packageConfig.getModuleName() + SymbolConst.SLASH + StringUtils.firstToLowerCase(tableInfo.getEntityName()) + ".html";
            }
        });
        focList.add(new FileOutConfig(Const.ELEM_TEMPLATE_JS) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return webOutDir + "/src/main/resources/static/" + packageConfig.getModuleName() + "/script/" + StringUtils.firstCharToLower(tableInfo.getEntityName()) + ".js";
            }
        });

        injectionConfig.setFileOutConfigList(focList);
        return injectionConfig;
    }

    /**
     * 自定义模板配置
     * @return
     */
    public TemplateConfig getTemplateConfig(){
        TemplateConfig templateConfig = new TemplateConfig();
        //指定自定义模板路径，注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
        templateConfig.setController("templates/backend/mybatis/controller.java");
        templateConfig.setEntity("templates/backend/mybatis/entity.java");
        templateConfig.setMapper("templates/backend/mybatis/mapper.java");
        templateConfig.setService("templates/backend/mybatis/service.java");
        templateConfig.setServiceImpl("templates/backend/mybatis/serviceImpl.java");
        templateConfig.setXml("templates/backend/mybatis/mapper.xml");
        return templateConfig;
    }

    public StrategyConfig getStrategyConfig(String[] selectedTableNames){
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // 设置实体类的基类
//        strategy.setSuperEntityClass("com.xxx.baseEntity");
        // 设置主键id
//        strategy.setSuperEntityColumns("id");
        // 默认开启lombok
        strategy.setEntityLombokModel(true);
        // 默认restController
        strategy.setRestControllerStyle(true);
        // 设置基础BaseCOntroller
//        strategy.setSuperControllerClass("com.ahccc.framework.mybatis.base.controller.BaseController");
        // 驼峰连字符
        strategy.setControllerMappingHyphenStyle(false);
//        strategy.setTablePrefix(pc.getModuleName() + "_");
        // 要生成的表名和类名
        strategy.setInclude(selectedTableNames);
        return strategy;
    }

    /**
     * 开放表信息、预留子类重写
     *
     * @param config 配置信息
     * @return ignore
     */
    private List<TableInfo> getAllTableInfoList(ConfigBuilder config) {
        return config.getTableInfoList();
    }

    /**
     * 预处理配置
     *
     * @param config 总配置信息
     * @return 解析数据结果集
     */
    private ConfigBuilder pretreatmentConfigBuilder(ConfigBuilder config) {
        /*
         * 注入自定义配置
         */
        if (null != injectionConfig) {
            injectionConfig.initMap();
            config.setInjectionConfig(injectionConfig);
        }
        /*
         * 表信息列表
         */
        List<TableInfo> tableList = this.getAllTableInfoList(config);
        for (TableInfo tableInfo : tableList) {
            /* ---------- 添加导入包 ---------- */
            if (config.getGlobalConfig().isActiveRecord()) {
                // 开启 ActiveRecord 模式
                tableInfo.setImportPackages(Model.class.getCanonicalName());
            }
            if (tableInfo.isConvert()) {
                // 表注解
                tableInfo.setImportPackages(TableName.class.getCanonicalName());
            }
            if (config.getStrategyConfig().getLogicDeleteFieldName() != null && tableInfo.isLogicDelete(config.getStrategyConfig().getLogicDeleteFieldName())) {
                // 逻辑删除注解
                tableInfo.setImportPackages(TableLogic.class.getCanonicalName());
            }
            if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(config.getStrategyConfig().getVersionFieldName())) {
                // 乐观锁注解
                tableInfo.setImportPackages(Version.class.getCanonicalName());
            }
            boolean importSerializable = true;
            if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(config.getSuperEntityClass())) {
                // 父实体
                tableInfo.setImportPackages(config.getSuperEntityClass());
                importSerializable = false;
            }
            if (config.getGlobalConfig().isActiveRecord()) {
                importSerializable = true;
            }
            if (importSerializable) {
                tableInfo.setImportPackages(Serializable.class.getCanonicalName());
            }
            // Boolean类型is前缀处理
            if (config.getStrategyConfig().isEntityBooleanColumnRemoveIsPrefix()) {
                tableInfo.getFields().stream().filter(field -> "boolean".equalsIgnoreCase(field.getPropertyType()))
                        .filter(field -> field.getPropertyName().startsWith("is"))
                        .forEach(field -> {
                            field.setConvert(true);
                            field.setPropertyName(StringUtils.removePrefixAfterPrefixToLower(field.getPropertyName(), 2));
                        });
            }
        }
        return config.setTableInfoList(tableList);
    }

    /**
     * 连接路径字符串
     *
     * @param parentDir   路径常量字符串
     * @param packageName 包名
     * @return 连接后的路径
     */
    private String joinPath(String parentDir, String packageName) {
        if (StringUtils.isEmpty(parentDir)) {
            parentDir = System.getProperty(ConstVal.JAVA_TMPDIR);
        }
        if (!StringUtils.endsWith(parentDir, File.separator)) {
            parentDir += File.separator;
        }
        packageName = packageName.replaceAll("\\.", StringPool.BACK_SLASH + File.separator);
        return parentDir + packageName;
    }


}